perm filename DVI1ED.WEB[ARK,TEX] blob sn#769058 filedate 1984-09-05 generic text, type C, neo UTF8
COMMENT ⊗   VALID 00016 PAGES
C REC  PAGE   DESCRIPTION
C00001 00001
C00003 00002	% DVI1ED extracts pages from format 1 DVI files.
C00005 00003	@* Introduction.
C00011 00004	@* The character set.
C00019 00005	@ Old DVI Format
C00022 00006	@* DVI format 1 process table
C00028 00007	@* Input from binary files.
C00040 00008	@* Output to binary files.
C00044 00009	@* Using the backpointers.
C00048 00010	@* Reading the postamble.
C00052 00011	@* Dialog.
C00055 00012	@* Output pages.
C00057 00013	@* Output postamble.
C00060 00014	@* The main program.
C00061 00015	@* System-dependent changes.
C00062 00016	@* Index.
C00063 ENDMK
C⊗;
% DVI1ED extracts pages from format 1 DVI files.
% written by Arthur Keller -- Sept. 84

% Here is TeX material that gets inserted after \input webmac
\def\hang{\hangindent 3em\indent\ignorespaces}
\font\ninerm=amr9
\let\mc=\ninerm % medium caps for names like PASCAL
\def\PASCAL{{\mc PASCAL}}

\def\(#1){} % this is used to make section names sort themselves better
\def\9#1{} % this is used for sort keys in the index

\def\title{DVI1ED}
\def\contentspagenumber{1}
\def\topofcontents{\null
	\def\titlepage{F} % include headline on the contents page
	\def\rheader{\mainfont\hfil \contentspagenumber}
	\vfill
	\centerline{\titlefont {\ttitlefont DVI1ED}: DVI format 1 page extracter}
	\vskip 15pt
	\centerline{(Version 1.0, September 1984)}
	\vfill}
\pageno=\contentspagenumber \advance\pageno by 1
@* Introduction.
The \.{DVI1ED} utility program reads and writes version 1 binary
device-independent (``\.{DVI}'')
files that are produced by document compilers such as \TeX78, and extracts
desired pages from them.
This file is based on \.{DVItype} program, which was designed by David Fuchs.
@↑Fuchs, David Raymond@>

The |banner| string defined here should be changed whenever \.{DVI1ED}
gets modified.

@d banner=='This is DVI1ED, Version 1.0' {printed when the program starts}

@ This program is written in standard \PASCAL, except where it is necessary
to use extensions; for example, \.{DVI1ED} must read files whose names
are dynamically specified, and that would be impossible in pure \PASCAL.
All places where nonstandard constructions are used have been listed in
the index under ``system dependencies.''
@!@↑system dependencies@>

Another extension is to use a default |case| as in \.{TANGLE}, \.{WEAVE},
etc.

@d othercases == others: {default for cases not listed explicitly}
@d endcases == @+end {follows the default case in an extended |case| statement}
@f othercases == else
@f endcases == end

@ The binary input comes from |dvi_in_file|, and the binary output is written
to |dvi_out_file| file. Diagnostic messages are written to 
\PASCAL's standard |output| file. The term |print| is used instead of
|write| when this program writes on |output|, so that all such output
could easily be redirected if desired.

@d print(#)==write(#)
@d print_ln(#)==write_ln(#)

@p program DVI1ED(@!dvi_in_file,@!dvi_out_file,@!output);
label @<Labels in the outer block@>@/
const @<Constants in the outer block@>@/
type @<Types in the outer block@>@/
var @<Globals in the outer block@>@/
procedure initialize; {this procedure gets things started properly}
	var i,j:integer; {loop index for initializations}
	begin print_ln(banner);@/
	@<Set initial values@>@/
	end;

@ If the program has to stop prematurely, it goes to the
`|final_end|'. Another label, |postamble|, is used when the postamble has
been reached while reading forwards.

@d final_end=9999 {label for the end of it all}
@d postamble=1000 {label for handling the postamble}

@<Labels...@>=final_end,postamble;

@ The following parameters can be changed at compile time to extend or
reduce \.{DVI1ED}'s capacity.

@<Constants...@>=
@!max_fonts=100; {maximum number of distinct fonts per \.{DVI} file}
@!line_length=79; {bracketed lines of output will be at most this long}
@!terminal_line_length=150; {maximum number of characters input in a single
	line of input from the terminal}
@!stack_size=100; {\.{DVI} files shouldn't |push| beyond this depth}
@!name_size=1000; {total length of all font file names}
@!name_length=50; {a file name shouldn't be longer than this}
@!max_pages=256; {maximum pages in a DVI file that we can handle}

@ Here are some macros for common programming idioms.

@d incr(#) == #←#+1 {increase a variable by unity}
@d decr(#) == #←#-1 {decrease a variable by unity}
@d do_nothing == {empty statement}

@ If the \.{DVI} file is badly malformed, the whole process must be aborted;
\.{DVI1ED} will give up, after issuing an error message about the symptoms
that were noticed.

Such errors might be discovered inside of subroutines inside of subroutines,
so a procedure called |jump_out| has been introduced. This procedure, which
simply transfers control to the label |final_end| at the end of the program,
contains the only non-local |goto| statement in \.{DVI1ED}.
@↑system dependencies@>

@d abort(#)==begin print(' ',#); jump_out;
		end
@d bad_dvi(#)==abort('Bad DVI file: ',#,'!')
@.Bad DVI file@>

@p procedure jump_out;
begin goto final_end;
end;
@* The character set.
Like all programs written with the  \.{WEB} system, \.{DVI1ED} can be
used with any character set. But it uses ASCII code internally, because
the programming for portable input-output is easier when a fixed internal
code is used, and because \.{DVI} files use ASCII code for file names
and certain other strings.

The next few sections of \.{DVI1ED} have therefore been copied from the
analogous ones in the \.{WEB} system routines. They have been considerably
simplified, since \.{DVI1ED} need not deal with the controversial
ASCII codes less than @'40. If such codes appear in the \.{DVI} file,
they will be printed as question marks.

@<Types...@>=
@!ASCII_code=" ".."~"; {a subrange of the integers}

@ The original \PASCAL\ compiler was designed in the late 60s, when six-bit
character sets were common, so it did not make provision for lower case
letters. Nowadays, of course, we need to deal with both upper and lower case
alphabets in a convenient way, especially in a program like \.{DVI1ED}.
So we shall assume that the \PASCAL\ system being used for \.{DVI1ED}
has a character set containing at least the standard visible characters
of ASCII code (|"!"| through |"~"|).

Some \PASCAL\ compilers use the original name |char| for the data type
associated with the characters in text files, while other \PASCAL s
consider |char| to be a 64-element subrange of a larger data type that has
some other name.  In order to accommodate this difference, we shall use
the name |text_char| to stand for the data type of the characters in the
output file.  We shall also assume that |text_char| consists of
the elements |chr(first_text_char)| through |chr(last_text_char)|,
inclusive. The following definitions should be adjusted if necessary.
@↑system dependencies@>

@d text_char == char {the data type of characters in text files}
@d first_text_char=0 {ordinal number of the smallest element of |text_char|}
@d last_text_char=127 {ordinal number of the largest element of |text_char|}

@<Types...@>=
@!text_file=packed file of text_char;

@ The \.{DVI1ED} processor converts between ASCII code and
the user's external character set by means of arrays |xord| and |xchr|
that are analogous to \PASCAL's |ord| and |chr| functions.

@<Globals...@>=
@!xord: array [text_char] of ASCII_code;
	{specifies conversion of input characters}
@!xchr: array [0..255] of text_char;
	{specifies conversion of output characters}

@ Under our assumption that the visible characters of standard ASCII are
all present, the following assignment statements initialize the
|xchr| array properly, without needing any system-dependent changes.

@<Set init...@>=
for i←0 to @'37 do xchr[i]←'?';
xchr[@'40]←' ';
xchr[@'41]←'!';
xchr[@'42]←'"';
xchr[@'43]←'#';
xchr[@'44]←'$';
xchr[@'45]←'%';
xchr[@'46]←'&';
xchr[@'47]←'''';@/
xchr[@'50]←'(';
xchr[@'51]←')';
xchr[@'52]←'*';
xchr[@'53]←'+';
xchr[@'54]←',';
xchr[@'55]←'-';
xchr[@'56]←'.';
xchr[@'57]←'/';@/
xchr[@'60]←'0';
xchr[@'61]←'1';
xchr[@'62]←'2';
xchr[@'63]←'3';
xchr[@'64]←'4';
xchr[@'65]←'5';
xchr[@'66]←'6';
xchr[@'67]←'7';@/
xchr[@'70]←'8';
xchr[@'71]←'9';
xchr[@'72]←':';
xchr[@'73]←';';
xchr[@'74]←'<';
xchr[@'75]←'=';
xchr[@'76]←'>';
xchr[@'77]←'?';@/
xchr[@'100]←'@@';
xchr[@'101]←'A';
xchr[@'102]←'B';
xchr[@'103]←'C';
xchr[@'104]←'D';
xchr[@'105]←'E';
xchr[@'106]←'F';
xchr[@'107]←'G';@/
xchr[@'110]←'H';
xchr[@'111]←'I';
xchr[@'112]←'J';
xchr[@'113]←'K';
xchr[@'114]←'L';
xchr[@'115]←'M';
xchr[@'116]←'N';
xchr[@'117]←'O';@/
xchr[@'120]←'P';
xchr[@'121]←'Q';
xchr[@'122]←'R';
xchr[@'123]←'S';
xchr[@'124]←'T';
xchr[@'125]←'U';
xchr[@'126]←'V';
xchr[@'127]←'W';@/
xchr[@'130]←'X';
xchr[@'131]←'Y';
xchr[@'132]←'Z';
xchr[@'133]←'[';
xchr[@'134]←'\';
xchr[@'135]←']';
xchr[@'136]←'↑';
xchr[@'137]←'_';@/
xchr[@'140]←'`';
xchr[@'141]←'a';
xchr[@'142]←'b';
xchr[@'143]←'c';
xchr[@'144]←'d';
xchr[@'145]←'e';
xchr[@'146]←'f';
xchr[@'147]←'g';@/
xchr[@'150]←'h';
xchr[@'151]←'i';
xchr[@'152]←'j';
xchr[@'153]←'k';
xchr[@'154]←'l';
xchr[@'155]←'m';
xchr[@'156]←'n';
xchr[@'157]←'o';@/
xchr[@'160]←'p';
xchr[@'161]←'q';
xchr[@'162]←'r';
xchr[@'163]←'s';
xchr[@'164]←'t';
xchr[@'165]←'u';
xchr[@'166]←'v';
xchr[@'167]←'w';@/
xchr[@'170]←'x';
xchr[@'171]←'y';
xchr[@'172]←'z';
xchr[@'173]←'{';
xchr[@'174]←'|';
xchr[@'175]←'}';
xchr[@'176]←'~';
for i←@'177 to 255 do xchr[i]←'?';

@ The following system-independent code makes the |xord| array contain a
suitable inverse to the information in |xchr|.

@<Set init...@>=
for i←first_text_char to last_text_char do xord[chr(i)]←@'40;
for i←" " to "~" do xord[xchr[i]]←i;
@ Old DVI Format
@d old_id_byte=1 {identifies the kind of \.{DVI} files described here}

@d oset_char_0=0 {typeset character 0 and move right} {|vertchar|}
@d oset_char_127=127 {typeset character 127 and move right} {|vertchar|}
@d onop=128 {no operation}
@d obop=129 {beginning of page}
@d oeop=130 {ending of page}
@d opost=131 {postamble beginning}
@d opush=132 {save the current positions}
@d opop=133 {restore previous positions}
@d oset_rule=134 {typeset a rule and move right} {|vertrule|}
@d oput_rule=135 {typeset a rule} {|horzrule|}
@d oput1=136 {typeset a character} {|horzchar|}
@d ofnt4=137 {set current font}
@d ow4=138 {move right and set |w|}
@d ow3=139 {move right and set |w|}
@d ow2=140 {move right and set |w|}
@d ow0=141 {move right by |w|}
@d ox4=142 {move right and set |x|}
@d ox3=143 {move right and set |x|}
@d ox2=144 {move right and set |x|}
@d ox0=145 {move right by |x|}
@d oy4=146 {move down and set |y|}
@d oy3=147 {move down and set |y|}
@d oy2=148 {move down and set |y|}
@d oy0=149 {move down by |y|}
@d oz4=150 {move down and set |z|}
@d oz3=151 {move down and set |z|}
@d oz2=152 {move down and set |z|}
@d oz0=153 {move down by |z|}
@d ofnt_num_0=154 {set current font to 0}
@d ofnt_num_63=217 {set current font to 63}
@d oundefined_command=218

@* DVI format 1 process table
@d max_DVI_code=255

@ Number of bytes to copy
@<Glob...@>=
DVI_copy: array [eight_bits] of eight_bits;

@ @<Set initial...@>=
for i←0 to max_DVI_code do
   DVI_copy[i]←0;  {copy no bytes as default}
for i←oset_char_0 to oset_char_127 do
   DVI_copy[i]←0; {|vertchar|n}
DVI_copy[onop]←0;
DVI_copy[obop]←40;
DVI_copy[oeop]←0;
DVI_copy[opost]←0;
DVI_copy[opush]←0;
DVI_copy[opop]←0;
DVI_copy[oset_rule]←8;
DVI_copy[oput_rule]←8;
DVI_copy[oput1]←1;
DVI_copy[ofnt4]←4;
DVI_copy[ow4]←4;
DVI_copy[ow3]←3;
DVI_copy[ow2]←2;
DVI_copy[ow0]←0;
DVI_copy[ox4]←4;
DVI_copy[ox3]←3;
DVI_copy[ox2]←2;
DVI_copy[ox0]←0;
DVI_copy[oy4]←4;
DVI_copy[oy3]←3;
DVI_copy[oy2]←2;
DVI_copy[oy0]←0;
DVI_copy[oz4]←4;
DVI_copy[oz3]←3;
DVI_copy[oz2]←2;
DVI_copy[oz0]←0;
for i←ofnt_num_0 to ofnt_num_63 do
   DVI_copy[i]←0;

@ Special processing code
@<Types...@>=
specials=(do_error, do_normal, do_nop, do_bop, do_eop, do_post,
  do_push, do_pop, do_font);

@ @<Glob...@>=
DVI_code: array [eight_bits] of specials;

@ @<Set initial...@>=
for i←0 to max_DVI_code do
   DVI_code[i]←do_error;  {error found as default}
for i←oset_char_0 to oset_char_127 do
   DVI_code[i]←do_normal; {|vertchar|n}
DVI_code[onop]←do_nop;
DVI_code[obop]←do_bop;
DVI_code[oeop]←do_eop;
DVI_code[opost]←do_post;
DVI_code[opush]←do_push;
DVI_code[opop]←do_pop;
DVI_code[oset_rule]←do_normal;
DVI_code[oput_rule]←do_normal;
DVI_code[oput1]←do_normal;
DVI_code[ofnt4]←do_error;  {if we encounter these, we'll handle them}
DVI_code[ow4]←do_normal;
DVI_code[ow3]←do_normal;
DVI_code[ow2]←do_normal;
DVI_code[ow0]←do_normal;
DVI_code[ox4]←do_normal;
DVI_code[ox3]←do_normal;
DVI_code[ox2]←do_normal;
DVI_code[ox0]←do_normal;
DVI_code[oy4]←do_normal;
DVI_code[oy3]←do_normal;
DVI_code[oy2]←do_normal;
DVI_code[oy0]←do_normal;
DVI_code[oz4]←do_normal;
DVI_code[oz3]←do_normal;
DVI_code[oz2]←do_normal;
DVI_code[oz0]←do_normal;
for i←ofnt_num_0 to ofnt_num_63 do
   DVI_code[i]←do_font;

@ Code to handle these cases
@<Handle a |DVI_cmd|@>=
case DVI_code[DVI_cmd] of
do_error: bad_dvi(DVI_cmd:1,' at location ',in_cmd_loc:1);
do_normal: begin
   put_byte(DVI_cmd);
   for i←1 to DVI_copy[DVI_cmd] do
      begin DVI_byte←get_byte; put_byte(DVI_byte); end;
   end;
do_nop: ;
do_bop: begin
   bop_loc←out_cur_loc;
   put_byte(DVI_cmd);
   for i←1 to DVI_copy[DVI_cmd] do
      begin DVI_byte←get_byte; put_byte(DVI_byte); end;
   i←signed_quad;  {ignore back pointer}
   put_quad(prev_bop_loc);
   prev_bop_loc←bop_loc;
   end;
do_eop:  put_byte(DVI_cmd);
do_post: abort ('Found postamble when reading pages.');
do_push: begin
   put_byte(DVI_cmd);
   incr(stack);
   if stack>max_stack then max_stack←stack;
   end;
do_pop: begin
   put_byte(DVI_cmd);
   decr(stack);
   if stack<0 then bad_dvi('stack underflow at location ',in_cmd_loc:1);
   end;
do_font: begin
   put_byte(DVI_cmd);
   font_used[DVI_cmd-ofnt_num_0]←true;
   for i←1 to DVI_copy[DVI_cmd] do
      begin DVI_byte←get_byte; put_byte(DVI_byte); end;
   end;
endcases;

@ Stack definitions
@<Glob...@>=
@!stack, @!max_stack: integer;

@ Initialize stack
@<Set initial...@>=
  max_stack←0;

@ Page pointers
@<Glob...@>=
  @!prev_bop_loc:integer;

@ Initialize page pointer
@<Set initial...@>=
  prev_bop_loc←-1;

@ Font used flags
@<Glob...@>=
@!font_used: array[0..max_fonts] of boolean;

@ Initialize font used flags
@<Set initial...@>=
  for i←0 to max_fonts do
      font_used[i]←false;
@* Input from binary files.
We have seen that a \.{DVI} file is a sequence of 8-bit bytes. The bytes
appear physically in what is called a `|packed file of 0..255|'
in \PASCAL\ lingo.

Packing is system dependent, and many \PASCAL\ systems fail to implement
such files in a sensible way (at least, from the viewpoint of producing
good production software).  For example, some systems treat all
byte-oriented files as text, looking for end-of-line marks and such
things. Therefore some system-dependent code is often needed to deal with
binary files, even though most of the program in this section of
\.{DVI1ED} is written in standard \PASCAL.
@↑system dependencies@>

One common way to solve the problem is to consider files of |integer|
numbers, and to convert an integer in the range $-2↑{31}\L x<2↑{31}$ to
a sequence of four bytes $(a,b,c,d)$ using the following code, which
avoids the controversial integer division of negative numbers:
$$\vbox{\halign{#\hfil\cr
|if x≥0 then a←x div @'100000000|\cr
|else begin x←(x+@'10000000000)+@'10000000000; a←x div @'100000000+128;|\cr
\quad|end|\cr
|x←x mod @'100000000;|\cr
|b←x div @'200000; x←x mod @'200000;|\cr
|c←x div @'400; d←x mod @'400;|\cr}}$$
The four bytes are then kept in a buffer and output one by one. (On 36-bit
computers, an additional division by 16 is necessary at the beginning.
Another way to separate an integer into four bytes is to use/abuse
\PASCAL's variant records, storing an integer and retrieving bytes that are
packed in the same place; {\sl caveat implementor!\/}) It is also desirable
in some cases to read a hundred or so integers at a time, maintaining a
larger buffer.

We shall stick to simple \PASCAL\ in this program, for reasons of clarity,
even if such simplicity is sometimes unrealistic.

@<Types...@>=
@!eight_bits=0..255; {unsigned one-byte quantity}
@!byte_file=packed file of eight_bits; {files that contain binary data}

@ The program deals with two binary file variables: |dvi_in_file| is the main
input file that we are translating into symbolic form, and |tfm_file| is
the current font metric file from which character-width information is
being read.

@<Glob...@>=
@!dvi_in_file:byte_file; {input \.{DVI} file (format 1)}
@!dvi_out_file:byte_file; {output \.{DVI} file (format 2)}
@!tfm_file:byte_file; {a font metric file}

@ To prepare these files for input, we |reset| them. An extension of
\PASCAL\ is needed in the case of |tfm_file|, since we want to associate
it with external files whose names are specified dynamically (i.e., not
known at compile time). The following code assumes that `|reset(f,s)|'
does this, when |f| is a file variable and |s| is a string variable that
specifies the file name. If |eof(f)| is true immediately after
|reset(f,s)| has acted, we assume that no file named |s| is accessible.
@↑system dependencies@>

@p procedure open_in_dvi_file; {prepares to read packed bytes in |dvi_in_file|}
begin reset(dvi_in_file);
in_cur_loc←0;
end;
@#
procedure open_tfm_file; {prepares to read packed bytes in |tfm_file|}
begin reset(tfm_file,cur_name);
end;

@ If you looked carefully at the preceding code, you probably asked,
``What are |in_cur_loc| and |cur_name|?'' Good question. They're global
variables: |in_cur_loc| is the number of the byte about to be read next from
|dvi_in_file|, and |cur_name| is a string variable that will be set to the
current font metric file name before |open_tfm_file| is called.

@<Glob...@>=
@!in_cur_loc:integer; {where we are about to look, in |dvi_in_file|}
@!cur_name:packed array[1..name_length] of char; {external name,
	with no lower case letters}

@ It turns out to be convenient to read four bytes at a time, when we are
inputting from \.{TFM} files. The input goes into global variables
|b0|, |b1|, |b2|, and |b3|, with |b0| getting the first byte and |b3|
the fourth.

@<Glob...@>=
@!b0,@!b1,@!b2,@!b3: eight_bits; {four bytes input at once}

@ The |read_tfm_word| procedure sets |b0| through |b3| to the next
four bytes in the current \.{TFM} file.
@↑system dependencies@>

@p procedure read_tfm_word;
begin read(tfm_file,b0); read(tfm_file,b1);
read(tfm_file,b2); read(tfm_file,b3);
end;

@ We shall use another set of simple functions to read the next byte or
bytes from |dvi_in_file|. There are seven possibilities, each of which is
treated as a separate function in order to minimize the overhead for
subroutine calls.
@↑system dependencies@>

@p function get_byte:integer; {returns the next byte, unsigned}
var b:eight_bits;
begin if eof(dvi_in_file) then get_byte←0
else	begin read(dvi_in_file,b); incr(in_cur_loc); get_byte←b;
	end;
end;
@#
function signed_byte:integer; {returns the next byte, signed}
var b:eight_bits;
begin read(dvi_in_file,b); incr(in_cur_loc);
if b<128 then signed_byte←b @+ else signed_byte←b-256;
end;
@#
function get_two_bytes:integer; {returns the next two bytes, unsigned}
var a,@!b:eight_bits;
begin read(dvi_in_file,a); read(dvi_in_file,b);
in_cur_loc←in_cur_loc+2;
get_two_bytes←a*256+b;
end;
@#
function signed_pair:integer; {returns the next two bytes, signed}
var a,@!b:eight_bits;
begin read(dvi_in_file,a); read(dvi_in_file,b);
in_cur_loc←in_cur_loc+2;
if a<128 then signed_pair←a*256+b
else signed_pair←(a-256)*256+b;
end;
@#
function get_three_bytes:integer; {returns the next three bytes, unsigned}
var a,@!b,@!c:eight_bits;
begin read(dvi_in_file,a); read(dvi_in_file,b); read(dvi_in_file,c);
in_cur_loc←in_cur_loc+3;
get_three_bytes←(a*256+b)*256+c;
end;
@#
function signed_trio:integer; {returns the next three bytes, signed}
var a,@!b,@!c:eight_bits;
begin read(dvi_in_file,a); read(dvi_in_file,b); read(dvi_in_file,c);
in_cur_loc←in_cur_loc+3;
if a<128 then signed_trio←(a*256+b)*256+c
else signed_trio←((a-256)*256+b)*256+c;
end;
@#
function signed_quad:integer; {returns the next four bytes, signed}
var a,@!b,@!c,@!d:eight_bits;
begin read(dvi_in_file,a); read(dvi_in_file,b); read(dvi_in_file,c); read(dvi_in_file,d);
in_cur_loc←in_cur_loc+4;
if a<128 then signed_quad←((a*256+b)*256+c)*256+d
else signed_quad←(((a-256)*256+b)*256+c)*256+d;
end;

@ Finally we come to the routines that do random reading.
The driver program below needs two such routines: |dvi_length| should
compute the total number of bytes in |dvi_in_file|, possibly also
causing |eof(dvi_in_file)| to be true; and |move_to_byte(n)|
should position |dvi_in_file| so that the next |get_byte| will read byte |n|,
starting with |n=0| for the first byte in the file.
@↑system dependencies@>

Such routines are, of course, highly system dependent. They are implemented
here in terms of two assumed system routines called |set_pos| and |cur_pos|.
The call |set_pos(f,n)| moves to item |n| in file |f|, unless |n| is
negative or larger than the total number of items in |f|; in the latter
case, |set_pos(f,n)| moves to the end of file |f|.
The call |cur_pos(f)| gives the total number of items in |f|, if
|eof(f)| is true; we use |cur_pos| only in such a situation.

@p function dvi_length:integer;
begin set_pos(dvi_in_file,-1); dvi_length←cur_pos(dvi_in_file);
end;
@#
procedure move_to_byte(n:integer);
begin set_pos(dvi_in_file,n); in_cur_loc←n;
end;
@* Output to binary files.

@p procedure open_out_dvi_file; {prepares to write packed bytes into |dvi_out_file|}
begin rewrite(dvi_out_file);
out_cur_loc←0;
end;

@ If you looked carefully at the preceding code, you probably asked,
``What is |out_cur_loc|?'' Good question. It's a global
variable: |out_cur_loc| is the number of the byte about to be write next from
|dvi_out_file|.

@<Glob...@>=
@!out_cur_loc:integer; {where we are about to look, in |dvi_out_file|}

@ We shall use another set of simple procedures to write the next byte or
bytes from |dvi_out_file|. There are seven possibilities, each of which is
treated as a separate procedure in order to minimize the overhead for
subroutine calls.
@↑system dependencies@>

@d dvi_out(#)==write(dvi_out_file,#)

@p procedure put_byte(@!x:integer); {writes the next byte, signed}
begin if x>0 then {convert to positive}
	begin x:=x+@'10000000000;
	x:=x+@'10000000000;
	end;
dvi_out(x mod @'400);
incr(out_cur_loc);
end;
@#
procedure put_two_bytes(@!x:integer); {writes the next two bytes, signed}
begin if x>0 then {convert to positive}
	begin x:=x+@'10000000000;
	x:=x+@'10000000000;
	end;
x←x mod @'200000; dvi_out(x div @'400);
dvi_out(x mod @'400);
out_cur_loc←out_cur_loc+2;
end;
@#
procedure put_three_bytes(@!x:integer); {writes the next three bytes, signed}
begin if x>0 then {convert to positive}
	begin x:=x+@'10000000000;
	x:=x+@'10000000000;
	end;
x←x mod @'100000000; dvi_out(x div @'200000);
x←x mod @'200000; dvi_out(x div @'400);
dvi_out(x mod @'400);
out_cur_loc←out_cur_loc+3;
end;
@#
procedure put_quad(@!x:integer); {writes the next four bytes, signed}
begin if x≥0 then dvi_out(x div @'100000000)
else	begin x:=x+@'10000000000;
	x:=x+@'10000000000;
	dvi_out((x div @'100000000) + 128);
	end;
x←x mod @'100000000; dvi_out(x div @'200000);
x←x mod @'200000; dvi_out(x div @'400);
dvi_out(x mod @'400);
out_cur_loc←out_cur_loc+4;
end;

@* Using the backpointers.
First comes a routine that illustrates how to find the postamble quickly.

@<Find the postamble, working back from the end@>=
n←dvi_length;
if n<53 then bad_dvi('only ',n:1,' bytes long');
@.only n bytes long@>
m←n-4;
repeat if m=0 then bad_dvi('all 223s');
@.all 223s@>
move_to_byte(m); k←get_byte; decr(m);
until k≠223;
if k≠old_id_byte then bad_dvi('ID byte is ',k:1);
@.ID byte is wrong@>
move_to_byte(m-3); q←signed_quad;
if (q<0)∨(q>m-33) then bad_dvi('post pointer ',q:1,' at byte ',m-3:1);
@.post pointer is wrong@>
move_to_byte(q); k←get_byte;
if k≠opost then bad_dvi('byte ',q:1,' is not post');
@.byte n is not post@>
post_loc←q; first_backpointer←signed_quad

@ Note that the last steps of the above code save the locations of the
the |post| byte and the final |bop|.  We had better declare these global
variables, together with another one that we will need shortly.

@<Glob...@>=
@!post_loc:integer; {byte location where the postamble begins}
@!first_backpointer:integer; {the pointer following |post|}
@!start_loc:integer; {byte location of the first page to process}
@!in_page_count:integer; {number of pages in input DVI file}
@!page_no:array [1..max_pages] of integer; {count 0 for page}
@!page_loc:array [1..max_pages] of integer; {input DVI loc for page}

@ The next little routine shows how the backpointers can be followed
to move through a \.{DVI} file in reverse order. Ordinarily a \.{DVI}-reading
program would do this only if it wants to print the pages backwards or
if it wants to find a specified starting page that is not necessarily the
first page in the file; otherwise it would of course be simpler and faster
just to read the whole file from the beginning.

@<Count the pages and move to the starting page@>=
q←post_loc; p←first_backpointer; start_loc←-1;
begin repeat
	{now |q| points to a |post| or |bop| command; |p≥0| is prev pointer}
	if p>q-46 then
		bad_dvi('page link ',p:1,' after byte ',q:1);
@.page link wrong...@>
	q←p; move_to_byte(q); k←get_byte;
	if k=obop then incr(in_page_count)
	else bad_dvi('byte ',q:1,' is not bop');
@.byte n is not bop@>
	page_loc[in_page_count]←q;
	page_no[in_page_count]←signed_quad;
	move_to_byte(q+41);
	p←signed_quad;
until p<0;
end;

@ @<Set init...@>=
in_page_count←0;

@* Reading the postamble.
Now imagine that we are reading the \.{DVI} file and positioned just
four bytes after the |post| command. That, in fact, is the situation,
when the following part of \.{DVI1ED} is called upon to read, translate,
and check the rest of the postamble.

@p procedure read_postamble;
var k:integer; {loop index}
@!i,@!p,@!q,@!m:integer; {general purpose registers}
begin
print_ln('Postamble starts at byte ',post_loc:1,'.');
@.Postamble starts at byte n@>
if signed_quad≠1 then
	print_ln('numerator not 1!');
@.numerator not 1@>
if signed_quad≠1 then
	print_ln('denominator not 1!');
@.denominator not 1@>
mag←signed_quad;
print_ln('Magnification is ', mag:1);
@.Magnification is n@>
max_v←signed_quad; max_h←signed_quad;@/
print_ln('maxv=',max_v:1,', maxh=',max_h:1);@/
@<Process the font definitions of the postamble@>;
@<Make sure that the end of the file is well-formed@>;
end;

@ @<Glob...@>=
mag,max_h,max_v:integer; {DVI file parameters}

@ When we get to the present code, the |post_post| command has
just been read.

@<Make sure that the end of the file is well-formed@>=
q←signed_quad;
if q≠post_loc then
	print_ln('bad postamble pointer in byte ',in_cur_loc-4:1,'!');
@.bad postamble pointer@>
m←get_byte;
if m≠old_id_byte then print_ln('identification in byte ',in_cur_loc-1:1,
@.identification...should be n@>
		' should be ',old_id_byte:1,'!');
k←in_cur_loc; m←223;
while (m=223)∧ not eof(dvi_in_file) do m←get_byte;
if not eof(dvi_in_file) then bad_dvi('signature in byte ',in_cur_loc-1:1,
@.signature...should be...@>
		' should be 223')
else if in_cur_loc<k+4 then
	print_ln('not enough signature bytes at end of file (',
@.not enough signature bytes...@>
		in_cur_loc-k:1,')');

@ @<Process the font definitions...@>=
k←signed_quad;
while k≠-1 do begin 
k←signed_quad; {cksum}
k←signed_quad; {mag}
k←get_byte; {len}
for i←1 to k do p←get_byte; {name}
k←signed_quad end;

@* Dialog.
@p procedure dialog;
var i,@!p,@!start,@!num:integer;
@!more,@!ok:boolean;
@!ch:char;
begin
write(ttyoutput,'DVI page numbers: ');
for p←in_page_count downto 1 do
    begin write(ttyoutput,page_no[p]:1,' ');
    if p mod 30=0 then write_ln(ttyoutput);
    end;
write_ln(ttyoutput);
p←in_page_count;
repeat
@<Ask what to output@>;
if more
    then begin
        while(p>0)∧(page_no[p]≠start) do p←p-1;
        if p<1
	   then write_ln(ttyoutput,'Page ',start:1,' not found.  Type "r" to restart.')
	   else begin
		write(ttyoutput,'Number of pages? ');
		read(tty,num);
		if p≥num then
		    for i←p downto p-num+1 do
			begin
			page_wanted[i]←true;
			write(ttyoutput,' [',page_no[i]:1,']');
			end
		    else write(ttyoutput,'Not enough pages, only ',i:1,' pages left.');
		p←p-num;
		write_ln(ttyoutput);
		end;
	end;
until not more or (i<1);
write_ln(ttyoutput);
write_ln(ttyoutput);
end;

@ @<Ask what to output@>=
repeat
    ok←true;
    write(ttyoutput,'Want more? (n,y,r) ');
    repeat read(tty,ch) until ch>' ';
    if (ch='n') or (ch='N') then more←false
    else if (ch='y') or (ch='Y') then more←true
    else if (ch='r') or (ch='R') then begin more←true; p←in_page_count; end
    else begin ok←false; write(ttyoutput,'"',ch,'" invalid, try again.'); end;
until ok;
if more then begin
    write(ttyoutput,'Starting page? ');
    read(tty,start);
    end;

@ @<Glob...@>=
@!page_wanted:array [1..max_pages] of boolean; {output this page}

@ @<Set init...@>=
for i←1 to max_pages do page_wanted[i]←false;
@* Output pages.
Here's the procedure to output a page.  The input pointer should be
at a |bop| command.

@p procedure copy_page;
var @!bop_loc: integer;
i,@!t:integer;
@!DVI_cmd:integer;
@!in_cmd_loc:integer;
@!DVI_byte:integer;
begin
in_cmd_loc←in_cur_loc;
repeat DVI_cmd←get_byte until DVI_cmd≠onop;
if DVI_cmd=opost then goto postamble
else if DVI_cmd≠obop then
   abort('BOP expected at ',out_cur_loc-1:1,', found ', DVI_cmd:1,' instead.');
incr(total_pages);
repeat
@<Handle...@>;
in_cmd_loc←in_cur_loc;
DVI_cmd←get_byte
until DVI_cmd=oeop;
put_byte(oeop);
end;

@ @<Glob...@>=
total_pages:integer;

@ @<Set init...@>=
total_pages←0;

@ @<Output pages@>=
print_ln('Sending pages to output.');
for k←in_page_count downto 1 do
    if page_wanted[k] then
	begin
	move_to_byte(page_loc[k]);
	print(' [',page_no[k]:1,']');
	copy_page;
	end;
@* Output postamble.
@<Output postamble@>=
move_to_byte(post_loc);
p←get_byte;	{read postamble starter}
if p≠opost then bad_dvi('expected postamble, found ',p:1);
npost_loc←out_cur_loc;
put_byte(opost);@/
p←signed_quad;
put_quad(prev_bop_loc);@/
k←signed_quad; {numerator}
put_quad(k);@/
k←signed_quad; {denominator}
put_quad(k);@/
mag←signed_quad;
put_quad(mag);@/
max_v←signed_quad;
put_quad(max_v);@/
max_h←signed_quad;
put_quad(max_h);@/
@<Output fonts@>;
put_quad(npost_loc);
put_byte(old_id_byte);
put_byte(223);
put_byte(223);
put_byte(223);
put_byte(223);
for k←(out_cur_loc mod 4) to 3 do put_byte(223); {fill out word}

@ @<Glob...@>=
npost_loc:integer;

@ @<Output fonts@>=
f←signed_quad;
while f≠-1 do begin
    print_ln(' ');
    print('Font ',f:1);
    if not font_used[f] then print(' not used');@/
    if font_used[f] then put_quad(f);@/
    k←signed_quad; if font_used[f] then put_quad(k); {checksum}
    k←signed_quad; if font_used[f] then put_quad(k); {mag}
    @<Process font name@>;
    if k≠1000 then print(' at ',k:1); {print mag}
    f←signed_quad;
    end;
put_quad(-1); {signal end of fonts}

@ @<Process font name@>=
{do not use |k| here}
n←get_byte;
put_byte(n);
print(' ');
for i←1 to n do
    begin
    in_font_name[i]←get_byte;
    print(xchr[in_font_name[i]]);
    if font_used[f]←put_byte(in_font_name[i]);
    end;

@ Place to store the font name
@d max_font_name_len=40
@<Glob...@>=
in_font_name:array[1..max_font_name_len] of integer;

@* The main program.
Now we are ready to put it all together. This is where \.{DVI1ED} starts,
and where it ends.

@p begin initialize; {get all variables initialized}
open_in_dvi_file;
open_out_dvi_file;
@<Find the postamble, working back from the end@>;
@<Count the pages and move to the starting page@>;
dialog; {set up all the options}
@<Output pages@>;
postamble:@<Output postamble@>;
final_end:end.

@ The main program needs a few global variables in order to do its work.

@<Glob...@>=
@!f,@!i,@!k,@!m,@!n,@!p,@!q:integer; {general purpose registers}

@* System-dependent changes.
This section should be replaced, if necessary, by changes to the program
that are necessary to make \.{DVI1ED} work at a particular installation.
It is usually best to design your change file so that all changes to
previous sections preserve the section numbering; then everybody's version
will be consistent with the printed program. More extensive changes,
which introduce new sections, can be inserted here; then only the index
itself will get a new section number.
@↑system dependencies@>
@* Index.
Pointers to error messages appear here together with the section numbers
where each ident\-i\-fier is used.